﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Threading.Tasks;
using System.Threading;

namespace ProducerConsumerPrinter
{
    public partial class Form1 : Form
    {
        private const int JobsLimit = 5;
        private const int NoJob     = 0;

        public Form1()
        {
            InitializeComponent();

            //initialize
            this.Jobs          = new Queue<string>();
            this.JobLabels     = new List<Label>();
            this.DoraemonOnOff = true;
            this.NinerOnOff    = true;
            this.PrinterOnOff  = true;

            this.JobLabels.Add(this.label1);
            this.JobLabels.Add(this.label2);
            this.JobLabels.Add(this.label3);
            this.JobLabels.Add(this.label4);
            this.JobLabels.Add(this.label5);
        }//end of constructor

        //Properties
        private Queue<string>           Jobs                      { get; set; }
        private List<Label>             JobLabels                 { get; set; }
        private bool                    NinerOnOff                { get; set; }
        private bool                    PrinterOnOff              { get; set; }
        private bool                    DoraemonOnOff             { get; set; }
        private CancellationTokenSource DoraCancelTokenSource     { get; set; }
        private CancellationTokenSource NinerCancelTokenSource    { get; set; }
        private CancellationTokenSource PrinterCancelTokernSource { get; set; }

        //Events
        private async void doraemonButton_Click(object sender, EventArgs e)
        {
            this.DoraemonOnOff = !this.DoraemonOnOff;

            if (this.DoraemonOnOff)
            {
                this.doraemonButton.Text = "On";
                
                //Request Doraemon to stop sending new job
                this.DoraCancelTokenSource.Cancel();
            }//end of this.DoraemonOnOff == on
            else
            {
                this.doraemonButton.Text   = "Off";
                this.DoraCancelTokenSource = new CancellationTokenSource();

                try
                {
                    //When it reaches await, it hits "pause" state and the 
                    //method execution is suspended.
                    //When the task being awaited completes, it hits the
                    //"play" state, and the method execution is resume
                    //http://msdn.microsoft.com/en-us/magazine/hh456403.aspx
                    await Task.Factory.StartNew
                    (
                        () =>
                        {
                            //Keeps enqueueing new job as long as the queue is not full
                            while (this.Jobs.Count < JobsLimit)
                            {
                                //Lock to avoid race condition
                                lock (this.Jobs)
                                {
                                    //Double checking
                                    if (this.Jobs.Count < JobsLimit)
                                    {
                                        //Listening to the line => this.DoraCancelTokenSource.Cancel();
                                        this.DoraCancelTokenSource.Token.ThrowIfCancellationRequested();
                                        this.Jobs.Enqueue("Doraemon");
                                        this.UpdateJobLabels();
                                        Thread.Sleep(1000);
                                    }//end of if (this.Jobs.Count < JobsLimit)
                                }//end of lock (this.Jobs)
                            }//end of while (this.Jobs.Count < JobsLimit)

                            this.doraemonButton.Text = "On";
                            this.DoraemonOnOff = !this.DoraemonOnOff;
                        }//end of anon method
                    );//end of new Task
                }//end of try await new task operation
                catch (OperationCanceledException)
                {
                    Console.WriteLine("Doraemon stops sending job");
                }//end of catch(OperationCanceledException)
            }//end of this.DoraemonOnOff == off
        }//end of private void doraemonButton_Click

        private async void ninerButton_Click(object sender, EventArgs e)
        {
            this.NinerOnOff = !this.NinerOnOff;

            if (this.NinerOnOff)
            {
                this.ninerButton.Text = "On";

                //Request Niner to stop sending printing job
                this.NinerCancelTokenSource.Cancel();
            }//ennd of if this.NinerOnOff == ON
            else
            {
                this.ninerButton.Text = "Off";
                this.NinerCancelTokenSource = new CancellationTokenSource();

                try
                {
                    await Task.Factory.StartNew
                    (
                        () =>
                        {
                            //Keeps sending new printing job as long as the queue is not full
                            while (this.Jobs.Count < JobsLimit)
                            {
                                //Lock the share memory to avoid race condition
                                lock(this.Jobs)
                                {
                                    //Double Checking
                                    if (this.Jobs.Count < JobsLimit)
                                    {
                                        this.NinerCancelTokenSource.Token.ThrowIfCancellationRequested();
                                        this.Jobs.Enqueue("Niner");
                                        this.UpdateJobLabels();
                                        Thread.Sleep(1000);
                                    }//end of if (this.Jobs.Count < JobsLimit)
                                }//end of lock(this.Jobs)
                            }//end of while (this.Jobs.Count < JobsLimit)

                            this.ninerButton.Text = "On";
                            this.NinerOnOff = ! this.NinerOnOff;
                        }//end of anon method
                    );//end of await Task.Factory.StartNew
                }//end of try
                catch (OperationCanceledException)
                {
                    Console.WriteLine("Niner stops sending printing job");
                }//end of catch (OperationCanceledException)
            }//end of if this.NinerOnOff == OFF
        }//end of private void ninerButton_Click

        private async void printerButton_Click(object sender, EventArgs e)
        {
            this.PrinterOnOff = !this.PrinterOnOff;

            if (this.PrinterOnOff)
            {
                this.printerButton.Text = "On";

                //Request Printing operation to be cancelled
                this.PrinterCancelTokernSource.Cancel();
            }//end of if this.PrinterOnOff == On
            else
            {
                this.printerButton.Text        = "Off";
                this.PrinterCancelTokernSource = new CancellationTokenSource();

                try
                {
                    await Task.Factory.StartNew
                    (
                        () =>
                        {
                            while (this.Jobs.Count > NoJob)
                            {
                                //Lock to avoid race condition
                                lock (this.Jobs)
                                {
                                    //Double checking
                                    if (this.Jobs.Count > NoJob)
                                    {
                                        //Listening to the line => this.PrinterCancelTokenSource.Cancel();
                                        this.PrinterCancelTokernSource.Token.ThrowIfCancellationRequested();
                                        this.Jobs.Dequeue();
                                        this.UpdateJobLabels();
                                        Thread.Sleep(1000);
                                    }//end of if (this.Jobs.Count > NoJob)
                                }//end of lock (this.Jobs)
                            }//end of while (this.Jobs.Count > NoJob)

                            this.printerButton.Text = "On";
                            this.PrinterOnOff = !this.PrinterOnOff;
                        }//end of anon method
                    );//end of await Task.Factory.StartNew
                }//end of try
                catch (OperationCanceledException)
                {
                    Console.WriteLine("Printer stops printing...");
                }//end of catch (OperationCanceledException)
            }//end of if this.PrinterOnOff == Off
        }//end of private void printerButton_Click        

        private void UpdateJobLabels()
        {
            var jobs = this.Jobs.ToArray<String>();

            this.ClearJobLabels();

            for (var index = 0; index < jobs.Length; index++)
            {
                this.JobLabels [index].Text = jobs [index];
            }//end of for (var index = 0; index < jobs.Length; index++)
        }//end of private void UpdateJobLabel()

        private void ClearJobLabels()
        {
            foreach (var jobLabel in JobLabels)
            {
                jobLabel.Text = String.Empty;
            }//end of foreach (var jobLabel in JobLabels)
        }//end of private void ClearJobLabels()
    }//end of class Form1
}//end of namespace ProducerConsumerPrinter
